/** @file
  SSDT for FVP PCIe

  Copyright (c) 2017 - 2021, Arm Ltd. All rights reserved.<BR>

  SPDX-License-Identifier: BSD-2-Clause-Patent

**/

#include "ArmPlatform.h"

/*
  See ACPI 6.1 Section 6.2.13

  There are two ways that _PRT can be used.

  In the first model, a PCI Link device is used to provide additional
  configuration information such as whether the interrupt is Level or
  Edge triggered, it is active High or Low, Shared or Exclusive, etc.

  In the second model, the PCI interrupts are hardwired to specific
  interrupt inputs on the interrupt controller and are not
  configurable. In this case, the Source field in _PRT does not
  reference a device, but instead contains the value zero, and the
  Source Index field contains the global system interrupt to which the
  PCI interrupt is hardwired.

  We use the first model with link indirection to set the correct
  interrupt type as PCI defaults (Level Triggered, Active Low) are not
  compatible with GICv2.
*/
#define LNK_DEVICE(Unique_Id, Link_Name, irq)                           \
  Device (Link_Name) {                                                  \
  Name (_HID, EISAID("PNP0C0F"))                                        \
  Name (_UID, Unique_Id)                                                \
  Name (_PRS, ResourceTemplate() {                                      \
    Interrupt (ResourceProducer, Level, ActiveHigh, Exclusive) { irq }  \
    })                                                                  \
  Method (_CRS, 0) { Return (_PRS) }                                    \
  Method (_SRS, 1) { }                                                  \
  Method (_DIS) { }                                                     \
}

#define PRT_ENTRY(Address, Pin, Link)                                                   \
  Package (4) {                                                                         \
    Address,  /* uses the same format as _ADR */                                        \
    Pin,      /* The PCI pin number of the device (0-INTA, 1-INTB, 2-INTC, 3-INTD)  */  \
    Link,     /* Interrupt allocated via Link device  */                                \
    Zero      /* global system interrupt number (no used) */                            \
}

/*
  See Reference [1] 6.1.1
  "High word Device #, Low word Function #. (for example, device 3,
  function 2 is 0x00030002). To refer to all the functions on a device #,
  use a function number of FFFF)."
*/
#define ROOT_PRT_ENTRY(Pin, Link)   PRT_ENTRY (0x0000FFFF, Pin, Link)  // Device 0 for Bridge.

DefinitionBlock ("SsdtPci.aml", "SSDT", 2, "ARMLTD", "FVP-REVC", 1) {
  Scope(_SB) {
    //
    // PCI Root Complex
    //
    LNK_DEVICE (1, LNKA, 168)
    LNK_DEVICE (2, LNKB, 169)
    LNK_DEVICE (3, LNKC, 170)
    LNK_DEVICE (4, LNKD, 171)

    Device(PCI0)
    {
      Name (_HID, EISAID ("PNP0A08")) // PCI Express Root Bridge
      Name (_CID, EISAID ("PNP0A03")) // Compatible PCI Root Bridge
      Name (_SEG, Zero) // PCI Segment Group number
      Name (_BBN, Zero) // PCI Base Bus Number
      Name (_CCA, 1)    // Initially mark the PCI coherent

      // Root Complex 0
      Device (RP0) {
        Name (_ADR, 0xF0000000)    // Dev 0, Func 0
      }

      // PCI Routing Table
      Name (_PRT, Package () {
        ROOT_PRT_ENTRY (0, LNKA),   // INTA
        ROOT_PRT_ENTRY (1, LNKB),   // INTB
        ROOT_PRT_ENTRY (2, LNKC),   // INTC
        ROOT_PRT_ENTRY (3, LNKD),   // INTD
        })
      // Root complex resources
      Method (_CRS, 0, Serialized) {
        Name (RBUF, ResourceTemplate () {
          WordBusNumber ( // Bus numbers assigned to this root
          ResourceProducer,
          MinFixed, MaxFixed, PosDecode,
          0,   // AddressGranularity
          0,   // AddressMinimum - Minimum Bus Number
          255, // AddressMaximum - Maximum Bus Number
          0,   // AddressTranslation - Set to 0
          256  // RangeLength - Number of Busses
          )

          DWordMemory ( // 32-bit BAR Windows
            ResourceProducer,
            PosDecode,
            MinFixed,
            MaxFixed,
            Cacheable,
            ReadWrite,
            0x00000000,   // Granularity
            0x50000000,   // Min Base Address
            0x57FFFFFF,   // Max Base Address
            0x00000000,   // Translate
            0x08000000    // Length
            )

          QWordMemory ( // 64-bit BAR Windows
            ResourceProducer,
            PosDecode,
            MinFixed,
            MaxFixed,
            Cacheable,
            ReadWrite,
            0x00000000,   // Granularity
            0x4000000000, // Min Base Address
            0x40FFFFFFFF, // Max Base Address
            0x00000000,   // Translate
            0x100000000   // Length
            )

          DWordIo ( // IO window
            ResourceProducer,
            MinFixed,
            MaxFixed,
            PosDecode,
            EntireRange,
            0x00000000,   // Granularity
            0x00000000,   // Min Base Address
            0x007fffff,   // Max Base Address
            0x5f800000,   // Translate
            0x00800000,   // Length
            ,
            ,
            ,
            TypeTranslation
            )
        }) // Name (RBUF)

        Return (RBUF)
      } // Method(_CRS)

      //
      // OS Control Handoff
      //
      Name (SUPP, Zero) // PCI _OSC Support Field value
      Name (CTRL, Zero) // PCI _OSC Control Field value

      /*
        See [1] 6.2.10, [2] 4.5
      */
      Method (_OSC,4) {
        // Check for proper UUID
        If(LEqual (Arg0,ToUUID ("33DB4D5B-1FF7-401C-9657-7441C03DD766"))) {
          // Create DWord-adressable fields from the Capabilities Buffer
          CreateDWordField (Arg3, 0, CDW1)
          CreateDWordField (Arg3, 4, CDW2)
          CreateDWordField (Arg3, 8, CDW3)

          // Save Capabilities DWord2 & 3
          Store (CDW2, SUPP)
          Store (CDW3, CTRL)

          // Only allow native hot plug control if OS supports:
          // * ASPM
          // * Clock PM
          // * MSI/MSI-X
          If(LNotEqual (And (SUPP, 0x16), 0x16)) {
            And (CTRL, 0x1E, CTRL) // Mask bit 0 (and undefined bits)
          }

          // Always allow native PME, AER (no dependencies)

          // Never allow SHPC (no SHPC controller in this system)
          And (CTRL, 0x1D, CTRL)

          If(LNotEqual (Arg1, One)) {  // Unknown revision
            Or (CDW1, 0x08, CDW1)
          }

          If(LNotEqual (CDW3, CTRL)) {  // Capabilities bits were masked
            Or (CDW1, 0x10, CDW1)
          }

          // Update DWORD3 in the buffer
          Store (CTRL,CDW3)
          Return (Arg3)
        } Else {
          Or (CDW1, 4, CDW1) // Unrecognized UUID
          Return (Arg3)
        }
      } // End _OSC
    } // PCI0
  }
}
